#pragma once

//搞个责任链出来
//1. 将client端输入的内容进行格式化处理
//2. 把格式化过的字符串写到一个文件内
//3. 规定每个文件最多只能有若干行，超过这么多行，就把该文件内所有的内容保存起来备份(切片备份)
//-》》保证每个文件内容量不超过某个限度
//4.进行打包成.tgz形式

#include "MsgQueue.hpp"
#include <ctime>
#include <sstream>
#include <filesystem>
#include <sys/wait.h>
#include <fstream>

#define FILE_MAXLINE 5 //写成5行方便测试

//通过一个入口，来进行后序的所有的节点对应的方法执行
// -》这个入口处必须想办法把所有的节点进行连接！
//为了指针连接方便(这里每个节点类各不相同，如果要指针链接，就很麻烦，需要人为的规定顺序，硬编码！)
//但是，如果使用子类继承父类的方式，那就简单了，只需要用基类指针指向子类即可！


#define default_enb 1 
//1代表节点打开 0为关闭

string pathname("./TMP/");
string filename("msg.log");

string Backuptime(to_string(0));

//父类//////////////////////////////////////////////////////////////////////////////
class Node{
public:
    Node(int enb = default_enb, shared_ptr<Node> next = nullptr)
        :_enb(enb), _next(next)
    {}
    ~Node(){}

    void LinkNextNode(shared_ptr<Node> next){
        _next = next;
    }

    void Enable(){
        _enb = 1;
    }

    void Disable(){
        _enb = 0;
    }

    bool IsEnable(){
        return (_enb == 1);
    }

    virtual void Execute(string& msg) = 0;

    void Is_Continue(string& msg){
        if(_next == nullptr){  // 先判断是否为空指针
            cout << "responsibie chain over!" << endl;
            return;
        }

        if(_next->IsEnable()){
            _next->Execute(msg);
        }
        else{
            cout << "responsibie chain over!" << endl;
            return;
        }
    }

// protected继承，方便子类使用
protected:
    int _enb;
    shared_ptr<Node> _next;
};

//四个节点//////////////////////////////////////////////////////////////////////////


class Format : public Node{
public:
    Format(){}
    ~Format(){}

    virtual void Execute(string& msg) override{
        if(!IsEnable()) {
            cout << "responsibie chain over!" << endl;
            return;
        }
        else{
            //形成的格式就是：(时间戳_发送信息的进程的pid_信息文本)
            cout << "1. 对client端输入的信息进行格式化处理: " << endl;
            stringstream ss;
            ss << "time[" << time(nullptr) << "]" 
            << "_pid[" <<  getpid() << "]" 
            << "_msg[" << msg.c_str() << "]\n" ;

            msg = ss.str();
            cout << msg << endl;
        }

        Is_Continue(msg);
    }

private:
};



class SaveFile : public Node{
public:
    SaveFile(const string& path_name = pathname, const string& file_name = filename)
        :_pathname(path_name), _filename(file_name)
    {
        //默认是在当前目录下进行文件的创建 但是会有创建新目录的需求

        //存在就不管
        if(filesystem::exists(_pathname)) return;

        //但是有可能会面临着目录不存在，需要提前创建一下目录
        try{
            filesystem::create_directory(_pathname);
        }
        catch(filesystem::filesystem_error& e){
            cerr << e.what() << endl;
        }
    }

    virtual void Execute(string& msg) override{
        //使用文件系统(c++17)标准来做
        if(!IsEnable()) {
            cout << "responsibie chain over!" << endl;
            return;
        }
        else{
            cout << "2. 对格式化的信息保存到文件: "  << (_pathname + _filename) << endl;
            //把指定的内容写到对应的文件
            string all_file_name = _pathname + _filename;
            //打开文件 追加模式ios::app
            ofstream out(all_file_name, ios::app);
            if(!out.is_open()){
                cerr << "file open (app mode) error" << endl;
                return;
            }

            //打开成功，就把内容写到文件内去
            //out.write(msg.c_str(), msg.size());
            out << msg;
            
            //关掉文件
            out.close();
        }

        Is_Continue(msg);
    }
private:
    string _pathname;//保存文件在哪个路径
    string _filename;//保存的文件名
};



class Backup : public Node{
public:
    Backup(const string& path_name = pathname, const string& file_name = filename)
        :_pathname(path_name), _filename(file_name)
    {}

    virtual void Execute(string& msg) override{
        
        if(!IsEnable()) {
            cout << "responsibie chain over!" << endl;
            return;
        }
        else{
            cout << "3. 检查文件: "  << (_pathname + _filename) << "是否需要备份" << endl;

            //首先先来检查当前是否已经超过文件最大行数
            bool line_size = Is_OutOfRange();
            if(line_size){
                //超出文件范围 需要进行备份
                File_backup();
            }
        }

        Is_Continue(msg);
    }


private:
    bool Is_OutOfRange(){
        int cnt = 0;
        string file = _pathname + _filename;
        ifstream in(file);
        if(!in.is_open()){
            cerr << "file open (in mode) error" << endl;
            return false;
        }
        string tmp;
        while(getline(in, tmp)){
            ++cnt;
        }
        in.close();
        return (cnt > FILE_MAXLINE);
    }
    
    void File_backup(){
        //其实在Linux系统下，备份这件事情是原子的 -> 即就是把原来的文件覆盖掉。
        //所以只需要调用重命名接口就可以了

        Backuptime = to_string(time(nullptr));
        string oldname = _pathname + _filename; //原来的名字
        string newname = oldname + "." + Backuptime + ".bak";
        filesystem::rename(oldname, newname);
        cout << "需要备份，将文件备份为" << newname << endl;
        //旧文件没了不要紧，后面写的时候会重新创建(app -> append追加选项)

        //设置此时备份的时间戳
    }


private:
    string _pathname;//保存文件在哪个路径
    string _filename;//保存的文件名
};



//打包需要注意的是，如果被打包的文件带有路径，解包的时候会解包到对应路径下，所以最好的办法是：
//进入备份文件所在目录打包（而且应该让子进程干这个事情）

class Package : public Node{
public:
    Package(const string& path_name = pathname, const string& file_name = filename)
        :_pathname(path_name), _filename(file_name)
    {}

    virtual void Execute(string& msg) override{

        //把要被备份的文件的路径
        string backup_path = _pathname;
        //打包文件名
        string tar_name = _filename + "." + Backuptime + ".tgz";
        //备份文件的文件名
        string backup_name = _filename + "." + Backuptime + ".bak";

        cout << "备份文件的路径: " << backup_path << endl;
        cout << "压缩包名: " << tar_name << endl;
        cout << "备份文件名: " << backup_name << endl;
        //sleep(10);

        if(!IsEnable()){
            cout << "responsibie chain over!" << endl;
            return;
        }
        else{
            cout << "4. 检查是否需要进行文件的打包" << endl;
            if(filesystem::exists(_pathname + _filename)){
                cout << "备份文件不存在，不需要进行打包" << endl;
                //不能判断备份文件名，会出错
                return;
            }
            
            cout << "将文件 " << backup_name << "打包为-> " << tar_name << endl;
            pid_t id = fork();
            if(id < 0){
                cerr << "child process create error!" << endl;
                return;
            }
            else if(id == 0){
                //先进入要备份的目录
                filesystem::current_path(backup_path);
                char cwd[1024] = {0};
                getcwd(cwd, 1024);
                cout << "子进程进入目录 " << cwd << endl;

                cout << "开始打包文件" << endl;
                execl("/usr/bin/tar", "tar", "-czf", tar_name.c_str(), backup_name.c_str(), NULL);
                cout << "文件打包失败" << endl;
                exit(23);
            }
            else{
                int rid = waitpid(id, nullptr, 0); 
                unlink((backup_path + backup_name).c_str());
            }
        }

        //这个搞了一个空指针解引用 -》 导致一直出问题
        Is_Continue(msg);

    }
private:
    string _pathname;//保存文件在哪个路径
    string _filename;//保存的文件名
};



//入口处，链接所有的节点
class Entry{
public:
    Entry()
    {
        _format = make_shared<Format>();
        _savefile = make_shared<SaveFile>();
        _backup = make_shared<Backup>();
        _package = make_shared<Package>();

        //定好顺序 -》 形成责任链
        _format->LinkNextNode(_savefile);
        _savefile->LinkNextNode(_backup);
        _backup->LinkNextNode(_package);
        _package->LinkNextNode(nullptr);

    }

    ~Entry(){}

    void EnableAll(){
        _format->Enable();
        _savefile->Enable();
        _backup->Enable();
        _package->Enable();
    }

    void DisableAll(){
        _format->Disable();
        _savefile->Disable();
        _backup->Disable();
        _package->Disable();
    }

    void EnableSelect(bool is_format, bool is_savefile, bool is_backup, bool is_package){
        (is_format == true) ? _format->Enable() : _format->Disable();
        (is_savefile == true) ? _savefile->Enable() : _savefile->Disable();
        (is_backup == true) ? _backup->Enable() : _backup->Disable();
        (is_package == true) ? _package->Enable() : _package->Disable();
    }

    void Start(string& msg){
        _format->Execute(msg);
        //后面的都让子类函数自己进行连接
    }

private:
    shared_ptr<Node> _format;
    shared_ptr<Node> _savefile;
    shared_ptr<Node> _backup;
    shared_ptr<Node> _package;
};
